/*
THIS FILE IS PART OF THE >>ADRENALIN-3D-GRAFIC-ENGINE<<
for latest info visit http://www.creative-games.de

Copyright (c) 2008-2009 by Marco Schneider

This library is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License (LGPL) as
published by the Free Software Foundation; either version 2.1 of the
License, or (at your option) any later version.

This library is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
License for more details.

*/

#ifndef __ADL_MATH_MATRIX44_H__
#define __ADL_MATH_MATRIX44_H__

#include "../Defines.h"

namespace Adrenalin
{
	namespace Math
	{
		template <class T=float>
		struct ADRENALIN_API Matrix44
		{
			union
			{
				struct	{
					T _11, _12, _13, _14;
					T _21, _22, _23, _24;
					T _31, _32, _33, _34;
					T _41, _42, _43, _44;
				};
				T _m[4][4];
				T _mm[16];
			};

			#pragma region Constructors
			Matrix44() {
				_12 = _13 = _14 = _21 = 0.0f;
				_23 = _24 = _31 = _32 = 0.0f;
				_34 = _41 = _42 = _43 = 0.0f;
				_11 = _22 = _33 = _44 = 1.0f;
			}
			Matrix44(const Matrix44& inMat)
			{
				(*this) = inMat;
			}
			Matrix44(const T in11, const T in12, const T in13, const T in14,
					 const T in21, const T in22, const T in23, const T in24,
					 const T in31, const T in32, const T in33, const T in34, 
					 const T in41, const T in42, const T in43, const T in44)
			{
				_11 = in11;
				_12 = in12;
				_13 = in13;
				_14 = in14;
				_21 = in21;
				_22 = in22;
				_23 = in23;
				_24 = in24;
				_31 = in31;
				_32 = in32;
				_33 = in33;
				_34 = in34;
				_41 = in41;
				_42 = in42;
				_43 = in43;
				_44 = in44;
			}
			#pragma endregion

			#pragma region Operators
			Matrix44 operator + ( const Matrix44&	inMat ) const
			{
				Matrix44 matReturn;

				matReturn._11 = _11 + inMat._11;
				matReturn._12 = _12 + inMat._12;
				matReturn._13 = _13 + inMat._13;
				matReturn._14 = _14 + inMat._14;

				matReturn._21 = _21 + inMat._21;
				matReturn._22 = _22 + inMat._22;
				matReturn._23 = _23 + inMat._23;
				matReturn._24 = _24 + inMat._24;

				matReturn._31 = _31 + inMat._31;
				matReturn._32 = _32 + inMat._32;
				matReturn._33 = _33 + inMat._33;
				matReturn._34 = _34 + inMat._34;

				matReturn._41 = _41 + inMat._41;
				matReturn._42 = _42 + inMat._42;
				matReturn._43 = _43 + inMat._43;
				matReturn._44 = _44 + inMat._44;

				return matReturn;
			}
			Matrix44 operator - ( const Matrix44&	inMat ) const
			{
				Matrix44 matReturn;

				matReturn._11 = _11 - inMat._11;
				matReturn._12 = _12 - inMat._12;
				matReturn._13 = _13 - inMat._13;
				matReturn._14 = _14 - inMat._14;

				matReturn._21 = _21 - inMat._21;
				matReturn._22 = _22 - inMat._22;
				matReturn._23 = _23 - inMat._23;
				matReturn._24 = _24 - inMat._24;

				matReturn._31 = _31 - inMat._31;
				matReturn._32 = _32 - inMat._32;
				matReturn._33 = _33 - inMat._33;
				matReturn._34 = _34 - inMat._34;

				matReturn._41 = _41 - inMat._41;
				matReturn._42 = _42 - inMat._42;
				matReturn._43 = _43 - inMat._43;
				matReturn._44 = _44 - inMat._44;

				return matReturn;
			}
			Matrix44 operator * ( const Matrix44&	inMat ) const
			{
			return Matrix44( _11*inMat._11 + _12*inMat._21 + _13*inMat._31 + _14*inMat._41,
								_11*inMat._12 + _12*inMat._22 + _13*inMat._32 + _14*inMat._42,
								_11*inMat._13 + _12*inMat._23 + _13*inMat._33 + _14*inMat._43,
								_11*inMat._14 + _12*inMat._24 + _13*inMat._34 + _14*inMat._44,
								_21*inMat._11 + _22*inMat._21 + _23*inMat._31 + _24*inMat._41,
								_21*inMat._12 + _22*inMat._22 + _23*inMat._32 + _24*inMat._42,
								_21*inMat._13 + _22*inMat._23 + _23*inMat._33 + _24*inMat._43,
								_21*inMat._14 + _22*inMat._24 + _23*inMat._34 + _24*inMat._44,
								_31*inMat._11 + _32*inMat._21 + _33*inMat._31 + _34*inMat._41,
								_31*inMat._12 + _32*inMat._22 + _33*inMat._32 + _34*inMat._42,
								_31*inMat._13 + _32*inMat._23 + _33*inMat._33 + _34*inMat._43,
								_31*inMat._14 + _32*inMat._24 + _33*inMat._34 + _34*inMat._44,
								_41*inMat._11 + _42*inMat._21 + _43*inMat._31 + _44*inMat._41,
								_41*inMat._12 + _42*inMat._22 + _43*inMat._32 + _44*inMat._42,
								_41*inMat._13 + _42*inMat._23 + _43*inMat._33 + _44*inMat._43,
								_41*inMat._14 + _42*inMat._24 + _43*inMat._34 + _44*inMat._44  );
			}
			Vector3<T>  operator * ( const Vector3<T>&  inVec ) const
			{
				Vector3<T> vecReturn;

				float fInvW = 1.0 / ( _41 * inVec.x + _42 * inVec.y + _43 * inVec.z + _44 );

				vecReturn.x = ( _11 * inVec.x + _12 * inVec.y + _13 * inVec.z + _14 ) * fInvW;
				vecReturn.y = ( _21 * inVec.x + _22 * inVec.y + _23 * inVec.z + _24 ) * fInvW;
				vecReturn.z = ( _31 * inVec.x + _32 * inVec.y + _33 * inVec.z + _34 ) * fInvW;

				return vecReturn;
			}
			void operator = ( const Matrix44& inMat )
			{
				_11	= inMat._11;	_12	= inMat._12;	_13	= inMat._13;	_14	= inMat._14;
				_21	= inMat._21;	_22	= inMat._22;	_23	= inMat._23;	_24	= inMat._24;
				_31	= inMat._31;	_32	= inMat._32;	_33	= inMat._33;	_34	= inMat._34;
				_41	= inMat._41;	_42	= inMat._42;	_43	= inMat._43;	_44	= inMat._44;
			}
			Matrix44 operator *= ( const Matrix44& inMat )
			{
				_11 = _11*inMat._11 + _12*inMat._21 + _13*inMat._31 + _14*inMat._41;
				_12 = _11*inMat._12 + _12*inMat._22 + _13*inMat._32 + _14*inMat._42;
				_13 = _11*inMat._13 + _12*inMat._23 + _13*inMat._33 + _14*inMat._43;
				_14 = _11*inMat._14 + _12*inMat._24 + _13*inMat._34 + _14*inMat._44;
				_21 = _21*inMat._11 + _22*inMat._21 + _23*inMat._31 + _24*inMat._41;
				_22 = _21*inMat._12 + _22*inMat._22 + _23*inMat._32 + _24*inMat._42;
				_23 = _21*inMat._13 + _22*inMat._23 + _23*inMat._33 + _24*inMat._43;
				_24 = _21*inMat._14 + _22*inMat._24 + _23*inMat._34 + _24*inMat._44;
				_31 = _31*inMat._11 + _32*inMat._21 + _33*inMat._31 + _34*inMat._41;
				_32 = _31*inMat._12 + _32*inMat._22 + _33*inMat._32 + _34*inMat._42;
				_33 = _31*inMat._13 + _32*inMat._23 + _33*inMat._33 + _34*inMat._43;
				_34 = _31*inMat._14 + _32*inMat._24 + _33*inMat._34 + _34*inMat._44;
				_41 = _41*inMat._11 + _42*inMat._21 + _43*inMat._31 + _44*inMat._41;
				_42 = _41*inMat._12 + _42*inMat._22 + _43*inMat._32 + _44*inMat._42;
				_43 = _41*inMat._13 + _42*inMat._23 + _43*inMat._33 + _44*inMat._43;
				_44 = _41*inMat._14 + _42*inMat._24 + _43*inMat._34 + _44*inMat._44;

				return *this;
			}
			#pragma endregion

			#pragma region Methodes
			Matrix44& Identity(void)
			{
				_12 = _13 = _14 = _21 = 0.0f;
				_23 = _24 = _31 = _32 = 0.0f;
				_34 = _41 = _42 = _43 = 0.0f;
				_11 = _22 = _33 = _44 = 1.0f;
				return *this;
			}
			Vector3<T>  TransformVector( const Vector3<T>&  inVec )
			{
				Vector3<T> vecReturn;

	//			float fInvW = 1.0 / ( _41 * inVec.x + _42 * inVec.y + _43 * inVec.z + _44 );

	//			vecReturn.x = ( _11 * inVec.x + _12 * inVec.y + _13 * inVec.z + _14 ) * fInvW;
	//			vecReturn.y = ( _21 * inVec.x + _22 * inVec.y + _23 * inVec.z + _24 ) * fInvW;
	//			vecReturn.z = ( _31 * inVec.x + _32 * inVec.y + _33 * inVec.z + _34 ) * fInvW;

	//			vecReturn.x = ( _11 * inVec.x + _12 * inVec.y + _13 * inVec.z + _14 );
	//			vecReturn.y = ( _21 * inVec.x + _22 * inVec.y + _23 * inVec.z + _24 );
	//			vecReturn.z = ( _31 * inVec.x + _32 * inVec.y + _33 * inVec.z + _34 );
	/*
				vecReturn.x = inVec.x * _11 + inVec.y * _21 + inVec.z * 31 + _41;
				vecReturn.y = inVec.x * _12 + inVec.y * _22 + inVec.z * 32 + _42;
				vecReturn.z = inVec.x * _13 + inVec.y * _23 + inVec.z * 33 + _43;
	*/
	//			float w = inVec.x * _14 + inVec.y * _24 + inVec.z * 34 + _44;
	//			float w = inVec.x * _41 + inVec.y * _42 + inVec.z * 43 + _44;

	//			vecReturn.x = vecReturn.x / w;
	//			vecReturn.y = vecReturn.y / w;
	//			vecReturn.z = vecReturn.z / w;
		
	/*			float w = _41 * inVec.x + _42 * inVec.y + _43 * inVec.z + _44;

				vecReturn.x = _11 * inVec.x + _12 * inVec.y + _13 * inVec.z + _14 * w;
				vecReturn.y = _21 * inVec.x + _22 * inVec.y + _23 * inVec.z + _24 * w;
				vecReturn.z = _31 * inVec.x + _32 * inVec.y + _33 * inVec.z + _34 * w;
	*/
				vecReturn.x = inVec.x * _11 + inVec.y * _21 + inVec.z * _31 + _41;
				vecReturn.y = inVec.x * _12 + inVec.y * _22 + inVec.z * _32 + _42;
				vecReturn.z = inVec.x * _13 + inVec.y * _23 + inVec.z * _33 + _43;

				return vecReturn;
			}

			void SetProjectionMatrixFovLH( const T TFOV, 
										   const T TAspect, 
										   const T TNear, 
										   const T TFar )
			{
				const T s = (T)(1 / tan(double(TFOV * 0.5)));
				const T Q = TFar / (TFar - TNear);

				_11	= s/TAspect;
				_12 = _13 = _14 = 0;

				_21 = 0;
				_22 = s;
				_23 = _24 = 0;

				_31 = _32 = 0;
				_33 = Q;
				_34 = 1;

				_41 = _42 = 0;
				_43 = -Q * TNear;
				_44 = 0;
			}//SetProjectionMatrixFovLH
			void SetCameraLookAtMatrix( const Adrenalin::Math::Vector3<float>& vecPosition,
										const Adrenalin::Math::Vector3<float>& vecLookAt,
										const Adrenalin::Math::Vector3<float>& vecUp )
			{
				Adrenalin::Math::Vector3<float> vecZAxis;
			
				vecZAxis = vecLookAt-vecPosition;
				vecZAxis.Normalize();

				Adrenalin::Math::Vector3<float> vecXAxis = vecUp.CrossProduct(vecZAxis);
				vecXAxis.Normalize();

				Adrenalin::Math::Vector3<float> vecYAxis = vecZAxis.CrossProduct(vecXAxis);

				_11 = vecXAxis.x;
				_12 = vecYAxis.x;
				_13 = vecZAxis.x;
				_14 = 0;

				_21 = vecXAxis.y;
				_22 = vecYAxis.y;
				_23 = vecZAxis.y;
				_24 = 0;

				_31 = vecXAxis.z;
				_32 = vecYAxis.z;
				_33 = vecZAxis.z;
				_34 = 0;

				_41 = -vecXAxis.DotProduct(vecPosition);
				_42 = -vecYAxis.DotProduct(vecPosition);
				_43 = -vecZAxis.DotProduct(vecPosition);
				_44 = 1.0f;
			}//SetCameraLookAtMatrix
			void SetTranslation( const Adrenalin::Math::Vector3<float>& vecTranslation )
			{
				_41	= vecTranslation.x;
				_42 = vecTranslation.y;
				_43 = vecTranslation.z;
				//_14 = vecTranslation.x;
				//_24 = vecTranslation.y;
				//_34 = vecTranslation.z;
			}//SetTranslation
			void SetScale( const Adrenalin::Math::Vector3<float>& vecScale )
			{
				_11	= vecScale.x;
				_22	= vecScale.y;
				_33	= vecScale.z;
			}//SetScale
			void SetRotationX( const float f )
			{
				_11 = 1.0f; _12 = 0.0f; _13 = 0.0f;	_14 = 0.0f;
				_21 = 0.0f;							_24 = 0.0f;
				_31 = 0.0f;                         _34 = 0.0f;
				_41 = 0.0f; _42 = 0.0f; _43 = 0.0f; _44 = 1.0f;

				_22 = _33 = cosf(f);
				_23 = sinf(f);
				_32 = -_23;
			}//SetRotationX
			void SetRotationY( const float f )
			{
							_12 = 0.0f;				_14 = 0.0f;
				_21 = 0.0f; _22 = 1.0f; _23 = 0.0f;	_24 = 0.0f;
							_32 = 0.0f;				_34 = 0.0f;
				_41 = 0.0f; _42 = 0.0f; _43 = 0.0f;	_44 = 1.0f;

				_11	= _33 = cosf(f);
				_31 = sinf(f);
				_13 = -_31;
			}//SetRotationY
			void SetRotationZ( const float f )
			{
										_13 = 0.0f;	_14 = 0.0f;
										_23 = 0.0f;	_24 = 0.0f;
				_31 = 0.0f;	_32 = 0.0f;	_33 = 1.0f;	_34 = 0.0f;
				_41 = 0.0f; _42 = 0.0f; _43 = 0.0f;	_44 = 1.0f;

				_11	= _22 = cosf(f);
				_12 = sinf(f);
				_21 = -_12;
			}//SetRotationZ
			void SetRotation( const Adrenalin::Math::Vector3<float> v )
			{
				this->SetRotationX( v.x );
				this->SetRotationY( v.y );
				this->SetRotationZ( v.z );
			}//SetRotation
			#pragma endregion
		};
	}//namespace Math
}//namespace Adrenalin
#endif